//! \file MlpNetwork.h
//! \brief Defines the <code>fullsail_ai::MlpNetwork</code> class.
//! \author Cromwell D. Enage
#ifndef _FULLSAIL_AI_MLP_NETWORK_H_
#define _FULLSAIL_AI_MLP_NETWORK_H_

#include <vector>
#include "NeuronLayer.h"

namespace fullsail_ai {

	//! \brief This class is responsible for organizing the feed-forward and back-propagation
	//! algorithms at a high level.
	//!
	//! Implementations of these algorithms, as well as other operations that this class provides,
	//! must work with the <code>NeuronLayer</code> interface only in order to access and/or
	//! modify the underlying weights and biases.  In turn, the <code>MlpAgent</code> class
	//! operations must work with this interface only in order to access and/or modify the
	//! underlying data.
	//!
	//! \note
	//!   - There is no input neuron layer.  Instead, inputs are stored in standard data containers
	//!     defined as vector of vector types.
	//!   - User code must store the generated output in a container outside this class.
	class MlpNetwork
	{
		NeuronLayer hiddenLayer;
		NeuronLayer outputLayer;

	public:
		//! \brief Returns an unmodifiable view of this network's hidden layer.
		//!
		//! \return  A <code>const</code> view of the hidden layer.
		inline NeuronLayer const& getHiddenLayer() const
		{
			return hiddenLayer;
		}

		//! \brief Returns an unmodifiable view of this network's output layer.
		//!
		//! \return  A <code>const</code> view of the output layer.
		inline NeuronLayer const& getOutputLayer() const
		{
			return outputLayer;
		}

		//! \brief Initializes this network.
		//!
		//! \param   numberGenerator    a zero-argument function that returns a different integer
		//!                             each time it is invoked.
		//! \param   inputCount         the number of inputs that the agent using this network
		//!                             must take into consideration.
		//! \param   hiddenNeuronCount  the number of neurons that the hidden layer will hold.
		//! \param   actionCount        the number of possible actions that the agent using this
		//!                             network may undertake.
		//!
		//! \pre
		//!   - The range of \a numberGenerator is <code>[0, RAND_MAX]</code>.
		//!   - <code>1 \<</code> \a inputCount
		//!   - <code>1 \<</code> \a hiddenNeuronCount
		//!   - <code>1 \<</code> \a actionCount
		//! \post
		//!   - <code>hiddenNeurons.size() ==</code> \a hiddenNeuronCount
		void initialize(int (*numberGenerator)(), std::size_t inputCount,
		                std::size_t hiddenNeuronCount, std::size_t actionCount);

		//! \brief Generates this network's outputs.
		//!
		//! \param   inputs   the container of input values that this network will evaluate.
		//! \param   outputs  the container in which the output values will be stored.
		void generateOutputs(std::vector<double> const& inputs,
		                     std::vector<double>& outputs) const;

		//! \brief Helper method for computing the errors of the output neurons.
		//!
		//! Use this function when the network needs to learn a particular training set.
		//!
		//! \param   expectedOutputs  the output desired by the training set.
		//! \param   actualOutputs    the current output of the network when it is given the
		//!                           training set's inputs.
		//! \param   outputErrors     the collection of error values to be computed for the output
		//!                           layer.
		//!
		//! \pre
		//!   - Exactly one element in the \a expectedOutputs container has a value of one;
		//!     all other elements have a value of zero.
		//!   - There are as many elements in the \a actualOutputs container as there are elements
		//!     in the \a expectedOutputs container.
		//!   - There are as many elements in the \a outputErrors container as there are elements
		//!     in the \a expectedOutputs container.
		static void computeOutputErrors(std::vector<double> const& expectedOutputs,
		                                std::vector<double> const& actualOutputs,
		                                std::vector<double>& outputErrors);

	private:
		//! \brief Helper method for computing the errors of the hidden neurons.
		//!
		//! \note
		//!   - You should call this method directly instead of
		//!     <code>NeuronLayer::computeHiddenErrors()</code> unless you really want to
		//!     figure out which layer to invoke that method on and why.
		//!
		//! \param   hiddenValues  the values of the hidden neurons
		//!                        during feed-forward.
		//! \param   outputErrors  the container of errors of the output neurons.
		//! \param   hiddenErrors  the container of error values to be computed for the hidden
		//!                        neurons.
		//!
		//! \pre
		//!   - There are as many elements in the \a outputErrors container as there are output
		//!     neurons.
		void computeHiddenErrors(std::vector<double> const& hiddenValues,
		                         std::vector<double> const& outputErrors,
		                         std::vector<double>& hiddenErrors) const;

	public:
		//! \brief Trains this network to learn the specified training sets.
		//!
		//! A training set is a pair consisting of a sample input value container and the
		//! corresponding expected output value container.
		//!
		//! \param   trainingSetInputs  a container of sample input value containers.
		//! \param   expectedOutputs    the container of corresponding values that the
		//!                             <code>feedForward()</code> method should output.
		//! \param   learningFactor     the rate at which this network makes progress in learning.
		//!
		//! \pre
		//!   - This network was previously initialized.
		//!   - All elements in \a trainingSetInputs are the same size, which was the input count
		//!     used to initialize this network.
		//!   - All elements in \a expectedOutputs are the same size, which was the action count
		//!     used to initialize this network.
		//!   - The size of \a trainingSetInputs is nonzero.
		//!   - There are as many training set inputs as there are expected outputs.
		//!   - For each output container in \a expectedOutputs, exactly one element has a value
		//!     of one; all other elements have a value of zero.
		//!   - <code>0 \<</code> \a learningFactor
		void learnTrainingSets(std::vector<std::vector<double> > const& trainingSetInputs,
		                       std::vector<std::vector<double> > const& expectedOutputs,
		                       double learningFactor);
	};
}  // namespace fullsail_ai

#endif  // _FULLSAIL_AI_MLP_NETWORK_H_

